package data
{
	import assets.smiley;
	
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.geom.Point;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import other.Utils;
	
	import view.PlotSurface;

	public class PlotSurfaceData extends EventDispatcher
	{
		
		public static const REDRAW_EVENT:String = "redrawEvent";
		
		public static var PX_PER_UNIT:int;
		
		//public const NUM_OBSTACLES:int = 10;
		//public const MIN_OBSTACLE_RADIUS:int = 5;
		//public const MAX_OBSTACLE_RADIUS:int = 100;
		
		public var UNIT_SIZE:int = 20;
		
		public static var WIDTH_PX:int;
		public static var HEIGHT_PX:int;
		
		// The ranges (in graph units)
		public const MIN_X:Number = -15;
		public const MAX_X:Number = 15;
		public const MIN_Y:Number = -15;
		public const MAX_Y:Number = 15;
		
		// The gap we should leave between pixel samples along the curve 
		public const PX_PER_FRAME:int = 2;
		
					
		// EXPLOSION_RADIUS measured in px
		// If you change this, you also have to change the size of the actual explosion animation.
		// And if that happens, then all the timing might screw up.
		// So you're better off not changing this :)
		public const EXPLOSION_RADIUS:Number = 25;
		
		public var obstacle_grid:Array;

		private var surface:PlotSurface;
		
		private var p1_units:Array;
		private var p2_units:Array;
		
		private var _all_units:Array;
		private var cur_unit_idx:int;

		
		
		
		public function PlotSurfaceData() {
			generateObstacles();
		}
		

		
		
		public function set plot_surface(new_surface:PlotSurface):void {
			surface = new_surface;
			
			surface.px_per_unit = PX_PER_UNIT;
			surface.explosion_radius = EXPLOSION_RADIUS;
			surface.unit_size = UNIT_SIZE;
			
			surface.initialiseSurface(WIDTH_PX, HEIGHT_PX);
			surface.drawObstacles(obstacle_grid);
		}
		
		
		
		
		public function set all_units(unit_array:Array):void {
			p1_units = unit_array[0];
			p2_units = unit_array[1];
			_all_units = p1_units.concat(p2_units);
			
			if (obstacle_grid == null) {
				// To set the unit positions, we need to know where the obstacles are
				throw new Error("Cannot set units before plot surface.");
			}
			
			setupUnits();
			surface.drawUnits(p1_units, p2_units);
		}
		
		
		

		private const UNIT_HIT_DIST:Number = 0.8;
		public function fire(source_player:UnitData, func:PlotFunction):void {
			var raw_points:Array = new Array();
			
			var start_pos_px:Point = Utils.translateCart2Px(source_player.pos);
			var px:Number, py:Number, cartX:Number;
			
			// Iterate through all x values (pixels), starting at 1
			var delta_x:Number = (source_player.shoots_right ? 1 : -1);
			for (px=start_pos_px.x ; px<WIDTH_PX && px>=0 ; px+=delta_x) {
				// Get the cartesian x value this pixel refers to
				cartX = Utils.translatePx2Cart(new Point(px, 0)).x;
				py = HEIGHT_PX - Utils.translateCart2Px(new Point(0, func.shifted(cartX, source_player.pos))).y;
				raw_points.push(new Point(px, py));
			}
			
			// Right now, rawPoints now contains one point per x pixel on the chart.
			//raw_points = [new Point(0,0), new Point(1,1), new Point(2,2), new Point(4,4), new Point(8,8), new Point(16,16), new Point(32,32), new Point(64,64), new Point(128,128), new Point(256, 256)];
			var sampled_points:Array = [raw_points[0]];
			
			// Start the index at 1 because we've already added the first point
			var i:int = 1;
			var num_sampled:int = 1;
			var dist_travelled:Number = PX_PER_FRAME;
			
			while (i < raw_points.length && raw_points[i].length < Infinity) {
				
				// If the distance we've travelled exceeds the limit at which we should draw a new point
				if (Point.distance(sampled_points[num_sampled-1], raw_points[i]) > PX_PER_FRAME) {
					var interp_dist:Number = PX_PER_FRAME;
					
					// The direction this velocity vector should go in is derived from the last sampled point and the current raw point
					var theta:Number = Math.atan2(raw_points[i].y - sampled_points[num_sampled-1].y, raw_points[i].x - sampled_points[num_sampled-1].x);
					
					// The midway point
					var interpolated:Point = new Point(
						sampled_points[num_sampled-1].x + interp_dist*Math.cos(theta),
						sampled_points[num_sampled-1].y + interp_dist*Math.sin(theta)
						);

					sampled_points.push(interpolated);
					num_sampled ++;
					
					if ((source_player.shoots_right && sampled_points[num_sampled-1].x < raw_points[i].x) || 
						(!source_player.shoots_right && sampled_points[num_sampled-1].x > raw_points[i].x)) {
						// i-- will be followed by i++ further down, which essentially means this raw point gets reconsidered, which is what we want
						i --;
					}
				}
				
				i ++;
			}

			// Stop once we hit an obstacle or the edge of the grid OR another unit (but not us)
			var important:int;
			var asCart:Point;
			var whichUnitDied:int = -1;
			for (important=0 ; important<sampled_points.length ; important++) {
				// Gets our point
				px = Math.round(sampled_points[important].x);
				py = Math.round(sampled_points[important].y);
				
				asCart = Utils.translatePx2Cart(sampled_points[important]);
				asCart.y = -asCart.y;
				
				// Checks for obstacle or edge collision
				if (px < 0 || px >= WIDTH_PX || py < 0 || py >= HEIGHT_PX || 
					obstacle_grid[px][py]) {
					break;
				}
				
				// Checks for player collision
				for (var uidx:int=0 ; uidx<this._all_units.length ; uidx++) {
					
					// We don't care about collisions with ourself
					if (uidx == cur_unit_idx) continue;
					
					// We also don't care about collisision with dead units (which are invisible)
					if (!_all_units[uidx].alive) continue;
					
					var missile_dist_from_unit:Number = Point.distance(asCart, _all_units[uidx].pos);

					if (missile_dist_from_unit < UNIT_HIT_DIST) {
						whichUnitDied = uidx; 
						break;
					}
				}
				
				if (whichUnitDied != -1) {
					break;
				}
			}
			sampled_points = sampled_points.slice(0, important);
			
			var last_point:Point = sampled_points[sampled_points.length - 1];

			// Clear out the obstacles that were destroyed by this explosion
			explode(last_point);
			
			surface.drawBeam(Utils.translateCart2Px(source_player.pos), sampled_points);
			
			function runExplosionRoutine():void {
				surface.removeEventListener(PlotSurface.ON_MISSILE_COLLISION, runExplosionRoutine);
				surface.drawExplosion(last_point, obstacle_grid);
			}
			
			function runUnitHitRoutine():void {
				surface.removeEventListener(PlotSurface.ON_MISSILE_COLLISION, runUnitHitRoutine);
				surface.drawExplosion(last_point, obstacle_grid);
				_all_units[whichUnitDied].alive = false;
				surface.killUnit(whichUnitDied);
			}
			
			if (whichUnitDied != -1) {
				surface.addEventListener(PlotSurface.ON_MISSILE_COLLISION, runUnitHitRoutine);
			} else {
				surface.addEventListener(PlotSurface.ON_MISSILE_COLLISION, runExplosionRoutine);
			}
		}
		
		
		
		
		/**
		 * Delete obstacles within the explosion radius of a given point.
		 */
		private function explode(exp_point:Point):void {
			
			for (var px:int=0 ; px<WIDTH_PX ; px++) {
				for (var py:int=0 ; py<HEIGHT_PX ; py++) {
					if (Point.distance(exp_point, new Point(px, py)) <= EXPLOSION_RADIUS) {
						// Remove the obstacle at this position. We just blew it up.
						obstacle_grid[px][py] = false;
					}
				}
			}
		}
		
		
		
		
		/**
		 * Generate the gridObstacles 2d array and fill it with some obstacles.
		 * Each boolean element in the grid represents whether a px on the plot is filled.
		 */
		// The threshold of a bitmap pixel in Perlin noise for it to be considered an obstacle
		private const OBSTACLE_PX_THRESHOLD:uint = 0xFFFFFF * 0.45;
		// The size of artifacts in Perlin noise (defined mathematically, not really relevant) 
		private const PERLIN_SIZE:int = 150;
		private function generateObstacles():void {
			var cx:int, cy:int;
			
			obstacle_grid = new Array();
			
			// Generate the initial obstacles on a BitmapData so we can take advantage of Flex's noise functions 
			var bmpd:BitmapData = new BitmapData(WIDTH_PX, HEIGHT_PX, false, 0x000000);
			
			// Generate Perlin noise on the bitmap data object
			var seed:int = Math.floor(Math.random() * 100);
			bmpd.perlinNoise(PERLIN_SIZE, PERLIN_SIZE, 8, seed, false, false, 7, true);
			
			// Initialise the 2D obstacle array with data from the bitmap data
			for (cx=0 ; cx<WIDTH_PX ; cx++) {
				obstacle_grid.push(new Array());
				for (cy=0 ; cy<HEIGHT_PX ; cy++){
					var isObstaclePx:Boolean = (bmpd.getPixel(cx, cy) >= OBSTACLE_PX_THRESHOLD);
					obstacle_grid[cx].push(isObstaclePx);
				}
			}

		}
		
		
		
		
		/**
		 * Position units on the Cartesian plane. One in each quadrant.
		 * The ones in quadrants, I, IV are player 1's.
		 * The ones in quadrants II, III are player 2's.
		 */
		private function setupUnits():void {
			var edge_offset:int = 1;
			
			p1_units[0].pos = getRandomEmpty(edge_offset+MIN_X, -edge_offset, edge_offset+MIN_Y, -edge_offset);
			p1_units[1].pos = getRandomEmpty(edge_offset+MIN_X, -edge_offset, edge_offset, MAX_Y-edge_offset);
			p2_units[0].pos = getRandomEmpty(edge_offset, MAX_X-edge_offset, edge_offset+MIN_Y, -edge_offset);
			p2_units[1].pos = getRandomEmpty(edge_offset, MAX_X-edge_offset, edge_offset, MAX_Y-edge_offset);
		}
		
		
		
		
		/**
		 * Get a random cell (in terms of Cartesian values) that doesn't contain an obstacle.
		 * Used to generate initial positions for units.
		 */
		private function getRandomEmpty(xs:int, xe:int, ys:int, ye:int):Point {
			var p:Point, px_p:Point;
			p = new Point(Utils.rand(xs, xe), Utils.rand(ys, ye));
			px_p = Utils.translateCart2Px(p);
			px_p.y = HEIGHT_PX - px_p.y;
			while (obstacle_grid[int(px_p.x)][int(px_p.y)]) {
				p = new Point(Utils.rand(xs, xe), Utils.rand(ys, ye));
				px_p = Utils.translateCart2Px(p);
				px_p.y = HEIGHT_PX - px_p.y;
			}
			return p;
		}
		
		
		
		
		public function setUnitTurn(unit_idx:int):void {
			surface.setUnitTurn(_all_units[unit_idx], unit_idx);
			this.cur_unit_idx = unit_idx;
		}


		
		
		public function get gridSize():Number {
			return PX_PER_UNIT;
		}
		
//		private function sendRefresh():void {
//			this.dispatchEvent(new Event(REDRAW_EVENT));
//		}
		
		
		
		

	}
}